﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Reflection;
using System.Collections;

namespace MVCPets.Extensions
{
  public static class ListExtensions
  {
    #region CSVMethods
    /// Method to write a delimited file, given a generic enumerable.
    ///
    /// The type T
    /// an enumerable of type T
    /// The destination file info
    /// The delimiter ("," for example)
    public static void WriteDelimitedFile<T>(this IEnumerable<T> list, FileInfo saveFile, string delimiter)
    {
      if (list == null) return;

      using (StreamWriter sw = saveFile.CreateText())
      {
        PropertyInfo[] props = typeof(T).GetProperties();
        var headerNames = props.Select(x => x.Name);
        sw.WriteLine(string.Join(delimiter, headerNames.ToArray()));

        foreach (T item in list)
        {
          T item1 = item; // to prevent access to modified closure

          var values = props
            .Select(x => x.GetValue(item1, null) ?? "") // the null coalescing operator, replace null with ""
            .Select(x => x.ToString())
            .Select(x => x.Contains(delimiter) ? "\"" + x + "\"" : x); // if a value contains the delimiter, surround with quotes

          sw.WriteLine(string.Join(delimiter, values.ToArray()));
        }

        sw.Close();
      }
    }

    /// <summary>
    /// Converts a generic list to a CSV string
    /// </summary>
    /// <param name="Value">
    /// Any generic list
    /// </param>
    /// <returns>
    /// String representation in CSV format of the value passed in.
    /// </returns>
    public static string ToCSV<T>(this List<T> Value)
    {
      return Value.ToDelimitedString(',');
    }

    /// <summary>
    /// Converts a generic list to a delimited string
    /// </summary>
    /// <param name="Value">
    /// Any generic list
    /// </param>
    /// /// <param name="delim">
    /// The delimiter to use
    /// </param>
    /// <returns>
    /// String representation in delimeted format of the value passed in.
    /// </returns>
    public static string ToDelimitedString<T>(this List<T> Value, char delim)
    {
      if (Value == null || Value.Count == 0) throw new Exception("List must not be null.");

      //get type from 0th member
      Type t = Value[0].GetType();
      StringBuilder sb = new StringBuilder();

      //make a new instance of the class name we figured out to get its props
      object o = Activator.CreateInstance(t);

      //gets all properties
      PropertyInfo[] props = o.GetType().GetProperties();

      //Header row
      for (int i = 0; i < props.Length; i++)
      {
        sb.Append(props[i].Name.ToUpper());

        if (i < props.Length) sb.Append(delim);
      }

      sb.Append(Environment.NewLine);

      //Data rows
      foreach (T item in Value)
      {
        //this acts as datacolumn
        foreach (PropertyInfo pi in props)
        {
          //this is the row+col intersection (the value)
          string whatToWrite =
              Convert.ToString(item.GetType()
                               .GetProperty(pi.Name)
                               .GetValue(item, null))
                               .Replace(delim, ' ') + delim;

          sb.Append(whatToWrite);
        }

        sb.Append(Environment.NewLine);
      }

      return sb.ToString();
    }
    #endregion

    #region Excel Stuff
    /// <summary>
    /// Converts a generic list to an Excel XML file
    /// </summary>
    /// <param name="Value">
    /// Any generic list
    /// </param>
    /// <returns>
    /// String representation in Excel XML format of the value passed in.
    /// </returns>
    public static string ToExcel<T>(this List<T> list)
    {
      if (list == null || list.Count == 0) throw new Exception("List must not be null.");

      //get type from 0th member
      Type t = list[0].GetType();
      StringBuilder sb = new StringBuilder();

      //make a new instance of the class name we figured out to get its props
      object o = Activator.CreateInstance(t);

      //gets all properties
      PropertyInfo[] props = o.GetType().GetProperties();

      // Get the Excel header (different from the column headers!)
      sb.Append(GetExcelHeader());

      // Column headers                        
      sb.Append("      <Row>").Append(Environment.NewLine);

      for (int i = 0; i < props.Length; i++)
      {
        sb.Append("        <Cell ss:Index=\"").Append((i + 1).ToString()).Append("\" ss:StyleID=\"headerStyle\">").Append(Environment.NewLine);
        sb.Append("          <Data ss:Type=\"String\">").Append(props[i].Name.ToUpper().Trim().Replace("&", "&amp;")).Append("</Data>").Append(Environment.NewLine);
        sb.Append("        </Cell>").Append(Environment.NewLine);
      }

      sb.Append("      </Row>").Append(Environment.NewLine);

      //Data rows            
      for (int i = 0; i < list.Count; i++)
      {
        var item = list[i];
        string style = string.Empty;

        if (i % 2 == 0) style = "alternatingItemStyle";
        else style = "itemStyle";

        //this acts as datacolumn
        sb.Append("      <Row>").Append(Environment.NewLine);

        for (int j = 0; j < props.Length; j++)
        {
          //this is the row + col intersection (the value)
          object value = null;

          try
          {
            var prop = item.GetType().GetProperty(props[j].Name);
            value = prop.GetValue(item, null);
          }
          catch { }
          string toWrite = value == null ? "" : value.ToString();

          sb.Append("        <Cell ss:Index=\"").Append(j + 1).Append("\" ss:StyleID=\"").Append(style).Append("\">").Append(Environment.NewLine);
          sb.Append("          <Data ss:Type=\"String\">").Append(toWrite.Replace("&", "&amp;").Replace("<BR>", Environment.NewLine)).Append("</Data>").Append(Environment.NewLine);
          sb.Append("        </Cell>").Append(Environment.NewLine);
        }

        sb.Append("      </Row>").Append(Environment.NewLine);
      }

      // get the Excel Footer
      sb.Append(GetExcelFooter());

      return sb.ToString();
    }

    /// <summary>
    /// Writes a generic list to disk as an Excel XML file
    /// </summary>
    /// <param name="Value">
    /// Any generic list
    /// </param>
    /// <param name="writer">
    /// The TextWriter to write the output to
    /// </param>
    /// <returns>
    /// n/a
    /// </returns>
    public static void ToExcelFile<T>(this List<T> list, TextWriter writer)
    {
      if (list == null || list.Count == 0) throw new Exception("List must not be null.");

      //get type from 0th member
      Type t = list[0].GetType();

      //make a new instance of the class name we figured out to get its props
      object o = Activator.CreateInstance(t);

      //gets all properties
      PropertyInfo[] props = o.GetType().GetProperties();

      // Get the Excel header (different from the column headers!)
      writer.Write(GetExcelHeader());

      // Column headers                        
      writer.Write("      <Row>");
      writer.Write(Environment.NewLine);

      for (int i = 0; i < props.Length; i++)
      {
        writer.Write("        <Cell ss:Index=\"");
        writer.Write((i + 1).ToString());
        writer.Write("\" ss:StyleID=\"headerStyle\">");
        writer.Write(Environment.NewLine);

        writer.Write("          <Data ss:Type=\"String\">");
        writer.Write(props[i].Name.ToUpper().Trim().Replace("&", "&amp;"));
        writer.Write("</Data>");
        writer.Write(Environment.NewLine);

        writer.Write("        </Cell>");
        writer.Write(Environment.NewLine);
      }

      writer.Write("      </Row>");
      writer.Write(Environment.NewLine);

      //Data rows            
      for (int i = 0; i < list.Count; i++)
      {
        var item = list[i];
        string style = string.Empty;

        if (i % 2 == 0) style = "alternatingItemStyle";
        else style = "itemStyle";

        //this acts as datacolumn
        writer.Write("      <Row>");
        writer.Write(Environment.NewLine);

        for (int j = 0; j < props.Length; j++)
        {
          //this is the row + col intersection (the value)
          string whatToWrite = Convert.ToString(item.GetType().GetProperty(props[j].Name).GetValue(item, null));

          writer.Write("        <Cell ss:Index=\"");
          writer.Write(j + 1);
          writer.Write("\" ss:StyleID=\"");
          writer.Write(style);
          writer.Write("\">");
          writer.Write(Environment.NewLine);

          writer.Write("          <Data ss:Type=\"String\">");
          writer.Write(whatToWrite.Replace("&", "&amp;").Replace("<BR>", Environment.NewLine));
          writer.Write("</Data>");
          writer.Write(Environment.NewLine);

          writer.Write("        </Cell>");
          writer.Write(Environment.NewLine);
        }

        writer.Write("      </Row>");
        writer.Write(Environment.NewLine);
      }

      // get the Excel Footer
      writer.Write(GetExcelFooter());

      writer.Flush();
      writer.Close();
    }

    public static List<T> CloneList<T>(this List<T> toClone)
    {
      List<T> toReturn = new List<T>();
      toClone.ForEach(R => toReturn.Add(R));
      return toReturn;
    }
    #endregion

    #region Private Helper Methods
    private static string GetExcelHeader()
    {
      StringBuilder sb = new StringBuilder();

      sb.Append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>").Append(Environment.NewLine);
      sb.Append("<?mso-application progid=\"Excel.Sheet\"?>").Append(Environment.NewLine);
      sb.Append("<Workbook xmlns:ss=\"urn:schemas-microsoft-com:office:spreadsheet\" xmlns:o=\"urn:schemas-microsoft-com:office:office\" xmlns:x=\"urn:schemas-microsoft-com:office:excel\" xmlns=\"urn:schemas-microsoft-com:office:spreadsheet\">").Append(Environment.NewLine);
      sb.Append("  <Styles>").Append(Environment.NewLine);
      sb.Append("    <Style ss:ID=\"headerStyle\">").Append(Environment.NewLine);
      sb.Append("      <Alignment></Alignment>").Append(Environment.NewLine);
      sb.Append("      <Font></Font>").Append(Environment.NewLine);
      sb.Append("      <NumberFormat></NumberFormat>").Append(Environment.NewLine);
      sb.Append("    </Style>").Append(Environment.NewLine);
      sb.Append("    <Style ss:ID=\"itemStyle\">").Append(Environment.NewLine);
      sb.Append("      <Alignment></Alignment>").Append(Environment.NewLine);
      sb.Append("      <Font></Font>").Append(Environment.NewLine);
      sb.Append("      <NumberFormat></NumberFormat>").Append(Environment.NewLine);
      sb.Append("    </Style>").Append(Environment.NewLine);
      sb.Append("    <Style ss:ID=\"dateItemStyle\">").Append(Environment.NewLine);
      sb.Append("      <Alignment></Alignment>").Append(Environment.NewLine);
      sb.Append("      <Font></Font>").Append(Environment.NewLine);
      sb.Append("      <NumberFormat ss:Format=\"General Date\"></NumberFormat>").Append(Environment.NewLine);
      sb.Append("    </Style>").Append(Environment.NewLine);
      sb.Append("    <Style ss:ID=\"alternatingItemStyle\">").Append(Environment.NewLine);
      sb.Append("      <Alignment></Alignment>").Append(Environment.NewLine);
      sb.Append("      <Font></Font>").Append(Environment.NewLine);
      sb.Append("      <NumberFormat></NumberFormat>").Append(Environment.NewLine);
      sb.Append("    </Style>").Append(Environment.NewLine);
      sb.Append("    <Style ss:ID=\"alternatingDateItemStyle\">").Append(Environment.NewLine);
      sb.Append("      <Alignment></Alignment>").Append(Environment.NewLine);
      sb.Append("      <Font></Font>").Append(Environment.NewLine);
      sb.Append("      <NumberFormat ss:Format=\"General Date\"></NumberFormat>").Append(Environment.NewLine);
      sb.Append("    </Style>").Append(Environment.NewLine);
      sb.Append("  </Styles>").Append(Environment.NewLine);
      sb.Append("  <Worksheet ss:Name=\"Worksheet1\">").Append(Environment.NewLine);
      sb.Append("    <Table>").Append(Environment.NewLine);

      return sb.ToString();
    }

    private static string GetExcelFooter()
    {
      StringBuilder sb = new StringBuilder();

      sb.Append("    </Table>").Append(Environment.NewLine);
      sb.Append("  </Worksheet>").Append(Environment.NewLine);
      sb.Append("</Workbook>").Append(Environment.NewLine);

      return sb.ToString();
    }
    #endregion
  }
}
